/**
  ******************************************************************************
  * @file    sym32l010_hal_flash.c
  * @author  AE Team
  * @version 1.0.3
  * @date    2024-05-28
  * @brief   FLASH HAL module driver.
  *
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2024 SIYIMicro.
  * All rights reserved.</center></h2>
  *
  *
  ******************************************************************************
  */


/******************************************************************************/
/* Include files                                                              */
/******************************************************************************/
#include "sym32l010_hal_flash.h"


/*******************************************************************************
  * @brief  设置FLASH的读等待周期
  * @param  HclkFreq:  Hclk的频率，以Hz为单位
  * @retval None
  * @note   调整系统时钟频率时需同步调整FLASH的访问速率(SYSCTRL_CR2.FLASHWAIT等同FLASH_CR2.WAIT)
  */
void HAL_FLASH_ConfigWaitCycle(uint32_t HclkFreq)
{
    uint32_t tmp32;

    if (HclkFreq <= 24000000UL)
    {
        tmp32 = 0UL << SYSCTRL_CR2_FLASHWAIT_Pos;
    }
    else if (HclkFreq <= 48000000UL)
    {
        tmp32 = 1UL << SYSCTRL_CR2_FLASHWAIT_Pos;
    }
    else
    {
        while (1) { ; }
    }

    REGBITS_MODIFY(SYM_SYSCTRL->CR2,
                   SYSCTRL_CR2_FLASHWAIT_Msk,
                   SYSCTRL_CR_KEY | tmp32);
}


/*******************************************************************************
  * @brief  获取当前的读保护等级
  * @param  None
  * @retval 当前读保护等级
  *         0 - 3
  */
uint8_t HAL_FLASH_GetReadOutLevel(void)
{
    return ((SYM_FLASH->CR1 & FLASH_CR1_SECURITY_Msk) >> FLASH_CR1_SECURITY_Pos);
}


/*******************************************************************************
  * @brief  设置MCU的读保护等级
  * @param  RdLevel: 目标读保护等级
  *                  0 - 3
  * @retval None
  * @note   将等级写入MCU后，MCU会自动重启并完成读保护等级修改
  *         如果从非0等级变为等级0，则MCU会擦除FLASH内的所有数据
  *         Level0，ISP可读写，SWD可读写
  *         Level1，ISP可降级，SWD可降级；数据不可读出
  *         Level2，ISP可降级，SWD无功能；数据不可读出
  *         Level3，ISP无功能，SWD无功能；数据不可读出
  */
void HAL_FLASH_SetReadOutLevel(uint8_t RdLevel)
{
    if (RdLevel <= 3)
    {
        if (HAL_FLASH_GetReadOutLevel() != RdLevel)
        {
            *((volatile uint32_t *)(0x4000431C)) = 0x5A50 | RdLevel;
            *((volatile uint32_t *)(0x40004370)) = FLASH_CR_KEY | 0xABCD; // MCU重启以更新读保护等级
            while (1) { ; }
        }
    }
}


/*******************************************************************************
  * @brief  解锁指定地址范围内所有页面
  * @param  StartAddr: 待解锁区域的起始地址
  * @param  EndAddr  : 待解锁区域的结束地址
  * @retval HAL_OK   : 代表成功
  *         HAL_ERROR: 代表失败
  */
HAL_StatusTypeDef HAL_FLASH_UnlockPages(uint32_t StartAddr, uint32_t EndAddr)
{
    uint32_t LockIdx_Start, LockIdx_End;

    if ((StartAddr > EndAddr) || (EndAddr > FLASH_ENDADDR))
    {
        return HAL_ERROR;
    }

    LockIdx_Start = StartAddr / FLASH_PAGE_SIZE / FLASH_PAGE_CNT_PER_LOCK;
    LockIdx_End   = EndAddr   / FLASH_PAGE_SIZE / FLASH_PAGE_CNT_PER_LOCK;

    while (LockIdx_Start <= LockIdx_End)
    {
        SYM_FLASH->PAGELOCK |= FLASH_CR_KEY | (1UL << LockIdx_Start);
        LockIdx_Start++;
    }

    return HAL_OK;
}


/*******************************************************************************
  * @brief  锁定所有页面
  * @param  None
  * @retval None
  */
void HAL_FLASH_LockPages(void)
{
    SYM_FLASH->PAGELOCK = FLASH_CR_KEY;
}


/*******************************************************************************
  * @brief  擦除指定地址范围内所有页面
  * @param  StartAddr: 待擦除区域的超始地址
  * @param  EndAddr  : 待擦除区域的结束地址
  * @retval HAL_OK   : 代表成功，
  *         HAL_ERROR: 代表失败，查询FLASH_ISR寄存器以获取擦除失败的原因
  * @note   擦除之前需要先解锁待擦除的页面。
  * @note   在执行编程、擦除操作时，需要使用校准的HSI时钟信号进行时序控制，请参见用户手册Flash章节。
  */
HAL_StatusTypeDef HAL_FLASH_ErasePages(uint32_t StartAddr, uint32_t EndAddr)
{
    uint32_t trimValue = (uint32_t)TRIMCODE_HSI_VALUE;

    if (((SYM_SYSCTRL->HSI & SYSCTRL_HSI_TRIM_Msk) - trimValue) > 3)  // 检查HSI的TRIM值是否正确
    {
        while (1) { ; }
    }
    if ((StartAddr > EndAddr) || (EndAddr > FLASH_ENDADDR))           // 检查操作地址
    {
        return HAL_ERROR;
    }

    StartAddr = StartAddr / FLASH_PAGE_SIZE * FLASH_PAGE_SIZE;        // 变成页面的起始地址
    EndAddr   = EndAddr   / FLASH_PAGE_SIZE * FLASH_PAGE_SIZE;        // 变成页面的结束地址

    SYM_FLASH->ICR = 0x00;                                            // Clear all int flag
    SYM_FLASH->CR1 = FLASH_CR_KEY  | 0x02;
    while (StartAddr <= EndAddr)
    {
        *((volatile uint32_t *)(StartAddr)) = 0x00;                   // Erase page
        while (SYM_FLASH->ISR & FLASH_ISR_BUSY_Msk) { ; }             // 等待操作完成
        StartAddr += FLASH_PAGE_SIZE;
    }

    SYM_FLASH->CR1 = FLASH_CR_KEY;

    if (SYM_FLASH->ISR == 0x00)
    {
        return HAL_OK;
    }
    else
    {
        return HAL_ERROR;
    }
}


/*******************************************************************************
  * @brief  向FLASH的指定地址写入一个字节数据
  * @param  WriteAddr: 待写入数据的FLASH的起始地址
  * @param  WrData   : 待写入的数据
  * @retval HAL_OK   : 代表成功，
  *         HAL_ERROR: 代表失败，查询FLASH_ISR寄存器以获取写入失败的原因
  * @note   写之前需要先解锁待写入的页面，并且待写入地址范围内应为全0xFF。
  * @note   在执行编程、擦除操作时，需要使用校准的HSI时钟信号进行时序控制，请参见用户手册Flash章节。
  */
HAL_StatusTypeDef HAL_FLASH_WriteByte(uint32_t WriteAddr, uint8_t WrData)
{
    uint32_t trimValue = (uint32_t)TRIMCODE_HSI_VALUE;

    if (((SYM_SYSCTRL->HSI & SYSCTRL_HSI_TRIM_Msk) - trimValue) > 3)  // 检查HSI的TRIM值是否正确
    {
        while (1) { ; }
    }
    if (WriteAddr <= FLASH_ENDADDR)
    {
        SYM_FLASH->CR1 = FLASH_CR_KEY | 0x01;

        uint32_t Count = 8;
        do
        {
            SYM_FLASH->ICR = 0x00;
            *((volatile uint8_t *)(WriteAddr)) = WrData;              // write byte
            while (SYM_FLASH->ISR & FLASH_ISR_BUSY_Msk) { ; }         // 等待操作完成
        }
        while ((SYM_FLASH->ISR & FLASH_ISR_PROG_Msk) && Count--);

        SYM_FLASH->CR1 = FLASH_CR_KEY;

        if (SYM_FLASH->ISR == 0x00)
        {
            return HAL_OK;
        }
    }

    return HAL_ERROR;
}


/*******************************************************************************
  * @brief  向FLASH的指定地址写入数据
  * @param  WriteAddr: 待写入数据的FLASH的起始地址
  * @param  pWrBuf   : 待写入的数据的指针
  * @param  WrByteCnt: 待写入的数据的字节数量
  * @retval HAL_OK   : 代表成功，
  *         HAL_ERROR: 代表失败，查询FLASH_ISR寄存器以获取写入失败的原因
  * @note   写之前需要先解锁待写入的页面，并且待写入地址范围内应为全0xFF。
  * @note   在执行编程、擦除操作时，需要使用校准的HSI时钟信号进行时序控制，请参见用户手册Flash章节。
  */
HAL_StatusTypeDef HAL_FLASH_WriteBytes(uint32_t WriteAddr, uint8_t *pWrBuf, uint32_t WrByteCnt)
{
    uint32_t trimValue = (uint32_t)TRIMCODE_HSI_VALUE;

    if (((SYM_SYSCTRL->HSI & SYSCTRL_HSI_TRIM_Msk) - trimValue) > 3)  // 检查HSI的TRIM值是否正确
    {
        while (1) { ; }
    }
    if ((WriteAddr + WrByteCnt) <= (FLASH_ENDADDR + 1))
    {
        SYM_FLASH->CR1 = FLASH_CR_KEY | 0x01;

        while (WrByteCnt)
        {
            uint32_t Count = 8;
            do 
            {
                SYM_FLASH->ICR = 0x00;                                // clear all int flag
                *((volatile uint8_t *)(WriteAddr)) = *pWrBuf;         // write byte
                while (SYM_FLASH->ISR & FLASH_ISR_BUSY_Msk) { ; }     // 等待操作完成
            }
            while ((SYM_FLASH->ISR & FLASH_ISR_PROG_Msk) && Count--);

            WriteAddr++;
            WrByteCnt--;
            pWrBuf++;
        }

        SYM_FLASH->CR1 = FLASH_CR_KEY;

        if (SYM_FLASH->ISR == 0x00)
        {
            return HAL_OK;
        }
    }

    return HAL_ERROR;
}


/*******************************************************************************
  * @brief  向FLASH的指定地址写入数据--按照Word对齐方式写入
  * @param  WriteAddr: 待写入数据的FLASH的起始地址
  * @param  pWrBuf   : 待写入的数据的指针
  * @param  WrWordCnt: 待写入数据的Word数量
  * @retval HAL_OK   : 代表成功，
  *         HAL_ERROR: 代表失败，查询FLASH_ISR寄存器以获取写入失败的原因
  * @note   起始地址WriteAddr必须要按照Word对齐。
  * @note   写之前需要先解锁待写入的页面，并且待写入地址范围内应为全0xFF。
  * @note   在执行编程、擦除操作时，需要使用校准的HSI时钟信号进行时序控制，请参见用户手册Flash章节。
  */
HAL_StatusTypeDef HAL_FLASH_WriteWords(uint32_t WriteAddr, uint32_t *pWrBuf, uint32_t WrWordCnt)
{
    uint32_t trimValue = (uint32_t)TRIMCODE_HSI_VALUE;

    if (((SYM_SYSCTRL->HSI & SYSCTRL_HSI_TRIM_Msk) - trimValue) > 3)  // 检查HSI的TRIM值是否正确
    {
        while (1) { ; }
    }
    if ((WriteAddr + WrWordCnt * 4) <= (FLASH_ENDADDR + 1))
    {
        if ((WriteAddr & 0x03) == 0)
        {
            SYM_FLASH->CR1 = FLASH_CR_KEY | 0x01;

            while (WrWordCnt)
            {
                uint32_t Count = 8;
                do
                {
                    SYM_FLASH->ICR = 0x00;                            // clear all int flag
                    *((volatile uint32_t *)(WriteAddr)) = *pWrBuf;    // write word
                    while (SYM_FLASH->ISR & FLASH_ISR_BUSY_Msk) { ; } // 等待操作完成
                }
                while ((SYM_FLASH->ISR & FLASH_ISR_PROG_Msk) && Count--);

                WriteAddr += 4;
                WrWordCnt--;
                pWrBuf++;
            }

            SYM_FLASH->CR1 = FLASH_CR_KEY;

            if (SYM_FLASH->ISR == 0x00)
            {
                return HAL_OK;
            }
        }
    }

    return HAL_ERROR;
}


/*******************************************************************************
  * @brief  从FLASH的指定地址读出一个字节数据
  * @param  ReadAddr: 待读出数据的FLASH的地址
  * @retval 返回读出一个字节数据
  */
uint8_t HAL_FLASH_ReadByte(uint32_t ReadAddr)
{
    uint8_t tmp8 = *((volatile uint8_t *)(ReadAddr));
    return tmp8;
}


/*******************************************************************************
  * @brief  从FLASH的指定地址读出数据
  * @param  ReadAddr : 待读出数据的FLASH的超始地址
  * @param  pRdBuf   : 待读出的数据的存放指针
  * @param  RdByteCnt: 待读出的数据的字节数量
  * @retval None
  */
void HAL_FLASH_ReadBytes(uint32_t ReadAddr, uint8_t *pRdBuf, uint32_t RdByteCnt)
{
    while (RdByteCnt)
    {
        *pRdBuf = *((volatile uint8_t *)(ReadAddr));
        RdByteCnt--;
        ReadAddr++;
        pRdBuf++;
    }
}


/*******************************************************************************
  * @brief  向OTP的指定地址写入数据
  * @param  WriteAddr: 待写入数据的OTP的起始地址
  * @param  pWrBuf   : 待写入的数据的指针
  * @param  WrByteCnt: 待写入的数据的字节数量
  * @retval HAL_OK   : 代表成功
  *         HAL_ERROR: 代表失败
  * @note   OTP区域的大小为22字节，掉电后数据不会丢失，每个字节一旦编程后不可再擦除。
  *         OTP的起始地址、终止地址请参见手册或sym32l010_hal_flash.h文件的定义。
  */
HAL_StatusTypeDef HAL_OTP_WriteBytes(uint32_t WriteAddr, uint8_t *pWrBuf, uint32_t WrByteCnt)
{
    // 验证地址是否合法
    if ((WriteAddr < OTP_STARTADDR) || ((WriteAddr + WrByteCnt - 1) > OTP_ENDADDR))
    {
        return HAL_ERROR;
    }

    SYM_FLASH->CR1 = FLASH_CR_KEY | 0x01;
    while (WrByteCnt)
    {
        *((volatile uint8_t *)(WriteAddr)) = *pWrBuf;                 // write byte
        while (SYM_FLASH->ISR & FLASH_ISR_BUSY_Msk) { ; }             // 等待操作完成

        if (*((volatile uint8_t *)(WriteAddr)) != *pWrBuf)
        {
            return HAL_ERROR;
        }
        WriteAddr++;
        WrByteCnt--;
        pWrBuf++;
    }
    SYM_FLASH->CR1 = FLASH_CR_KEY;

    return HAL_OK;
}


/************************ (C) COPYRIGHT SIYIMicro *****END OF FILE*************/
